package storage

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// LocalConfig 本地存储配置
type LocalConfig struct {
	RootPath   string `yaml:"root_path"`   // 根路径
	BaseURL    string `yaml:"base_url"`    // 基础URL
	CreatePath bool   `yaml:"create_path"` // 是否自动创建路径
}

// GetStorageType 获取存储类型
func (c *LocalConfig) GetStorageType() StorageType {
	return StorageTypeLocal
}

// LocalStorage 本地存储实现
type LocalStorage struct {
	config *LocalConfig
}

// NewLocalStorage 创建本地存储实例
func NewLocalStorage(config *LocalConfig) (*LocalStorage, error) {
	if config == nil {
		return nil, errors.New("本地存储配置不能为空")
	}

	if config.RootPath == "" {
		return nil, errors.New("本地存储根路径不能为空")
	}

	// 确保根路径存在
	if err := os.MkdirAll(config.RootPath, 0755); err != nil {
		return nil, fmt.Errorf("创建本地存储根路径失败: %w", err)
	}

	return &LocalStorage{
		config: config,
	}, nil
}

// Upload 上传文件
func (s *LocalStorage) Upload(ctx context.Context, name string, reader io.Reader, option *UploadOption) (*FileInfo, error) {
	if option == nil {
		option = &UploadOption{}
	}

	// 确定存储路径
	path := name
	if option.Path != "" {
		path = filepath.Join(option.Path, name)
	}

	// 规范化路径，避免路径遍历攻击
	path = filepath.Clean(path)
	if strings.HasPrefix(path, "..") {
		return nil, errors.New("非法的文件路径")
	}

	// 完整的文件路径
	fullPath := filepath.Join(s.config.RootPath, path)

	// 确保目录存在
	dir := filepath.Dir(fullPath)
	if s.config.CreatePath {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return nil, fmt.Errorf("创建目录失败: %w", err)
		}
	}

	// 创建文件
	file, err := os.Create(fullPath)
	if err != nil {
		return nil, fmt.Errorf("创建文件失败: %w", err)
	}
	defer file.Close()

	// 写入文件内容
	size, err := io.Copy(file, reader)
	if err != nil {
		return nil, fmt.Errorf("写入文件失败: %w", err)
	}

	// 获取文件信息
	fileInfo, err := file.Stat()
	if err != nil {
		return nil, fmt.Errorf("获取文件信息失败: %w", err)
	}

	// 确定内容类型
	contentType := option.ContentType
	if contentType == "" {
		// 尝试通过文件扩展名猜测内容类型
		contentType = getContentTypeByExt(filepath.Ext(name))
	}

	// 构建访问URL
	url := s.buildURL(path)

	return &FileInfo{
		Name:         name,
		Path:         path,
		URL:          url,
		Size:         size,
		ContentType:  contentType,
		LastModified: fileInfo.ModTime(),
	}, nil
}

// Delete 删除文件
func (s *LocalStorage) Delete(ctx context.Context, path string) error {
	// 规范化路径，避免路径遍历攻击
	path = filepath.Clean(path)
	if strings.HasPrefix(path, "..") {
		return errors.New("非法的文件路径")
	}

	// 完整的文件路径
	fullPath := filepath.Join(s.config.RootPath, path)

	// 检查文件是否存在
	_, err := os.Stat(fullPath)
	if os.IsNotExist(err) {
		return nil // 文件不存在，视为删除成功
	}
	if err != nil {
		return fmt.Errorf("检查文件是否存在失败: %w", err)
	}

	// 删除文件
	if err := os.Remove(fullPath); err != nil {
		return fmt.Errorf("删除文件失败: %w", err)
	}

	return nil
}

// GetURL 获取文件访问URL
func (s *LocalStorage) GetURL(ctx context.Context, path string, expiration time.Duration) (string, error) {
	// 本地存储不支持临时URL，忽略过期时间
	return s.buildURL(path), nil
}

// GetFileInfo 获取文件信息
func (s *LocalStorage) GetFileInfo(ctx context.Context, path string) (*FileInfo, error) {
	// 规范化路径，避免路径遍历攻击
	path = filepath.Clean(path)
	if strings.HasPrefix(path, "..") {
		return nil, errors.New("非法的文件路径")
	}

	// 完整的文件路径
	fullPath := filepath.Join(s.config.RootPath, path)

	// 获取文件信息
	fileInfo, err := os.Stat(fullPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("文件不存在: %w", err)
		}
		return nil, fmt.Errorf("获取文件信息失败: %w", err)
	}

	if fileInfo.IsDir() {
		return nil, errors.New("指定路径是一个目录，不是文件")
	}

	// 获取文件名
	name := filepath.Base(path)

	// 确定内容类型
	contentType := getContentTypeByExt(filepath.Ext(name))

	// 构建访问URL
	url := s.buildURL(path)

	return &FileInfo{
		Name:         name,
		Path:         path,
		URL:          url,
		Size:         fileInfo.Size(),
		ContentType:  contentType,
		LastModified: fileInfo.ModTime(),
	}, nil
}

// ListFiles 列出指定目录下的文件
func (s *LocalStorage) ListFiles(ctx context.Context, prefix string) ([]*FileInfo, error) {
	// 规范化路径，避免路径遍历攻击
	prefix = filepath.Clean(prefix)
	if strings.HasPrefix(prefix, "..") {
		return nil, errors.New("非法的文件路径")
	}

	// 完整的目录路径
	fullPath := filepath.Join(s.config.RootPath, prefix)

	// 检查目录是否存在
	fileInfo, err := os.Stat(fullPath)
	if err != nil {
		if os.IsNotExist(err) {
			return []*FileInfo{}, nil // 目录不存在，返回空列表
		}
		return nil, fmt.Errorf("检查目录是否存在失败: %w", err)
	}

	// 如果指定路径是一个文件，则返回该文件的信息
	if !fileInfo.IsDir() {
		info, err := s.GetFileInfo(ctx, prefix)
		if err != nil {
			return nil, err
		}
		return []*FileInfo{info}, nil
	}

	// 遍历目录
	var files []*FileInfo
	err = filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 跳过目录
		if info.IsDir() {
			return nil
		}

		// 计算相对路径
		relPath, err := filepath.Rel(s.config.RootPath, path)
		if err != nil {
			return err
		}

		// 替换Windows路径分隔符为URL路径分隔符
		relPath = filepath.ToSlash(relPath)

		// 获取文件名
		name := filepath.Base(relPath)

		// 确定内容类型
		contentType := getContentTypeByExt(filepath.Ext(name))

		// 构建访问URL
		url := s.buildURL(relPath)

		files = append(files, &FileInfo{
			Name:         name,
			Path:         relPath,
			URL:          url,
			Size:         info.Size(),
			ContentType:  contentType,
			LastModified: info.ModTime(),
		})

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("遍历目录失败: %w", err)
	}

	return files, nil
}

// Type 获取存储类型
func (s *LocalStorage) Type() StorageType {
	return StorageTypeLocal
}

// buildURL 构建访问URL
func (s *LocalStorage) buildURL(path string) string {
	// 确保baseURL以/结尾
	baseURL := s.config.BaseURL
	if baseURL != "" && !strings.HasSuffix(baseURL, "/") {
		baseURL += "/"
	}

	// 确保path不以/开头
	path = strings.TrimPrefix(path, "/")

	return baseURL + path
}

// getContentTypeByExt 根据文件扩展名获取内容类型
func getContentTypeByExt(ext string) string {
	ext = strings.ToLower(ext)
	switch ext {
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".png":
		return "image/png"
	case ".gif":
		return "image/gif"
	case ".webp":
		return "image/webp"
	case ".svg":
		return "image/svg+xml"
	case ".pdf":
		return "application/pdf"
	case ".doc":
		return "application/msword"
	case ".docx":
		return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
	case ".xls":
		return "application/vnd.ms-excel"
	case ".xlsx":
		return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
	case ".ppt":
		return "application/vnd.ms-powerpoint"
	case ".pptx":
		return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
	case ".txt":
		return "text/plain"
	case ".html", ".htm":
		return "text/html"
	case ".css":
		return "text/css"
	case ".js":
		return "application/javascript"
	case ".json":
		return "application/json"
	case ".xml":
		return "application/xml"
	case ".zip":
		return "application/zip"
	case ".rar":
		return "application/x-rar-compressed"
	case ".7z":
		return "application/x-7z-compressed"
	case ".mp3":
		return "audio/mpeg"
	case ".mp4":
		return "video/mp4"
	case ".avi":
		return "video/x-msvideo"
	case ".mov":
		return "video/quicktime"
	default:
		return "application/octet-stream"
	}
}
